/* Copyright (c) 2007  Peter Palotas
 *  
 *  This software is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *  
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *  
 *      1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 *  
 *      2. Altered source versions must be plainly marked as such, and must not be
 *      misrepresented as being the original software.
 *  
 *      3. This notice may not be removed or altered from any source
 *      distribution.
 *  
 *  
 *  $Id: ConsoleProcess.cs 21 2007-08-17 18:54:41Z palotas $
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Security.Permissions;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using Plossum.IO;
using Microsoft.Win32.SafeHandles;

namespace Plossum.Win32
{
    /// <summary>
    /// Provides access to and enables you to start and stop local system console processes. This class provides
    /// a different set of startup flags and features than that of <see cref="T:System.Diagnostics.Process"/> more
    /// tailored towards working with local console processes.
    /// </summary>
    [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
    public class ConsoleProcess : IDisposable
    {
        #region Public types

        /// <summary>
        /// Defines the type of event to be sent by <see cref="GenerateConsoleControlEvent"/>
        /// </summary>
        public enum ControlEvent : int
        {
            /// <summary>
            /// Represents the user pressing Ctrl-C in the console
            /// </summary>
            CtrlC = UnsafeNativeMethods.CTRL_C_EVENT,
            /// <summary>
            /// Represents the user pressing Ctrl-Break in the console
            /// </summary>
            Break = UnsafeNativeMethods.CTRL_BREAK_EVENT
        }
        
        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ConsoleProcess"/> class.
        /// </summary>
        public ConsoleProcess()
        {
            mProcess = new Process();
            mStartInfo = new ConsoleProcessStartInfo();
            mProcess.Exited += Exited;
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Gets the base priority of the associated process. 
        /// </summary>
        /// <value>The base priority, which is computed from the PriorityClass of the associated process.</value>
        /// <exception cref="InvalidOperationException">The process has exited or the process has not started, so there is no process ID.</exception>
        public int BasePriority
        {
            get { return mProcess.BasePriority; }
        }

        /// <summary>
        /// Gets or sets whether the Exited event should be raised when the process terminates. 
        /// </summary>
        /// <value><c>true</c> true if the Exited event should be raised when the associated process is terminated 
        /// (through either an exit or a call to Kill); otherwise, <c>false</c>. The default is <c>false</c>.</value>
        /// <remarks>The EnableRaisingEvents property indicates whether the component should be notified 
        /// when the operating system has shut down a process. The EnableRaisingEvents property is used in 
        /// asynchronous processing to notify your application that a process has exited. To force your 
        /// application to synchronously wait for an exit event (which interrupts processing of the 
        /// application until the exit event has occurred), use the WaitForExit method.
        /// <para>When an associated process exits after being shut down by the operation system either through a normal or abnormal termination, the operating system notifies each process component to which the process was associated, as long as the component's EnableRaisingEvents value is true. If a component started the process, the component can then access the administrative information for the associated process, which is still being stored by the operating system. Such information includes the ExitTime, and the ExitCode.</para>
        /// <para>After the associated process exits, the Handle of the component no longer points to an existing process resource. Instead, it can be used only to access the operating system's information about the process resource. The operating system is aware that there are handles to exited processes that have not been released by Process components, so it keeps the ExitTime and Handle information in memory.</para>
        /// <para>There is a cost associated with watching for a process to exit. If EnableRaisingEvents is true, the Exited event is raised when the associated process terminates. The procedures that you have specified for the Exited event run at that time.</para>
        /// <para>Sometimes, your application starts a process but does not need to be notified of its closure. For example, your application can start Notepad to allow the user to perform text editing, but make no further use of the Notepad application. You can choose to not be notified when the process exits, because it is not relevant to the continued operation of your application. Setting EnableRaisingEvents to false saves system resources.</para>
        /// </remarks>
        public bool EnableRaisingEvents
        {
            get { return mProcess.EnableRaisingEvents; }
            set { mProcess.EnableRaisingEvents = value; }
        }

        /// <summary>
        /// Gets the value that the associated process specified when it terminated. 
        /// </summary>
        /// <value>The code that the associated process specified when it terminated.</value>
        /// <exception cref="InvalidOperationException">The process has not exited or the process <see cref="Handle"/> is not valid. </exception>
        /// <remarks><para>Use ExitCode to get the status that the system process returned when it exited. You can use the exit code much like an integer return value from a main() procedure. </para>
        /// <para>The ExitCode value for a process reflects the specific convention implemented by the application developer for that process. If you use the exit code value to make decisions in your code, be sure that you know the exit code convention used by the application process.</para>
        /// <para>Developers usually indicate a successful exit by an ExitCode value of zero, and designate errors by nonzero values that the calling method can use to identify the cause of an abnormal process termination. It is not necessary to follow these guidelines, but they are the convention.</para>
        /// <para>If you try to get the ExitCode before the process has exited, the attempt throws an exception. Examine the HasExited property first to verify whether the associated process has terminated.</para>
        /// <para>There are two ways of being notified when the associated process exits: synchronously and asynchronously. Synchronous notification relies on calling the WaitForExit method to pause the processing of your application until the associated component exits. Asynchronous notification relies on the Exited event. In either case, EnableRaisingEvents must be set to true for the Process component to receive notification that the process has exited.</para>
        /// </remarks>
        public int ExitCode
        {
            get 
            { 
                int exitCode;
                if (!UnsafeNativeMethods.GetExitCodeProcess(mHandle.DangerousGetHandle(), out exitCode))
                {
                    throw new Win32Exception();
                }
                return exitCode; 
            }
        }

        /// <summary>
        /// Gets the time that the associated process exited. 
        /// </summary>
        /// <value>A <see cref="System.DateTime"/> that indicates when the associated process was terminated.</value>
        /// <remarks>If the process has not terminated, attempting to retrieve the <see cref="ExitTime"/> property throws an exception. Use <see cref="HasExited"/> before getting the ExitTime property to determine whether the associated process has terminated.</remarks>
        public DateTime ExitTime
        {
            get { return mProcess.ExitTime; }
        }

        /// <summary>
        /// Returns the associated process's native handle. 
        /// </summary>
        /// <value>The handle that the operating system assigned to the associated process when the process was started. The system uses this handle to keep track of process attributes.</value>
        /// <exception cref="InvalidOperationException">The process has not been started. The Handle property cannot be read because there is no process associated with this Process instance, or, the Process instance has been attached to a running process but you do not have the necessary permissions to get a handle with full access rights. </exception>
        /// <exception cref="NotSupportedException">You are trying to access the Handle property for a process running on a remote computer.</exception>
        public SafeHandle Handle
        {
            get { return mHandle; }
        }

        /// <summary>
        /// Gets the number of handles opened by the process. 
        /// </summary>
        /// <value>The number of operating system handles the process has opened. </value>
        public int HandleCount
        {
            get { return mProcess.HandleCount; }
        }

        /// <summary>
        /// Gets a value indicating whether the associated process has been terminated. 
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the operating system process referenced by the Process component has terminated; otherwise, <c>false</c>.
        /// </value>
        /// <exception cref="InvalidOperationException">There is no process associated with the object.</exception>
        /// <exception cref="Win32Exception">The exit code for the process could not be retrieved. </exception>
        public bool HasExited
        {
            get { return mProcess.HasExited; }
        }

        /// <summary>
        /// Gets the unique identifier for the associated process. 
        /// </summary>
        /// <value>The system-generated unique identifier of the process that is referenced by this Process instance.</value>
        /// <exception cref="InvalidOperationException">The process's Id property has not been set, or, there is no process associated with this Process object.</exception>
        public int Id
        {
            get { return mProcess.Id; }
        }

        /// <summary>
        /// Gets the name of the computer the associated process is running on. 
        /// </summary>
        /// <value>The name of the computer that the associated process is running on. </value>
        /// <exception cref="InvalidOperationException">There is no process associated with this ConsoleProcess object.</exception>
        public string MachineName
        {
            get { return mProcess.MachineName; }
        }

        /// <summary>
        /// Gets the main module for the associated process. 
        /// </summary>
        /// <value>The <see cref="System.Diagnostics.ProcessModule"/> that was used to start the process</value>
        /// <exception cref="NotSupportedException">You are attempting to access this property for a process on a remote computer. </exception>
        /// <remarks>A process module represents a.dll or .exe file that is loaded into a particular process. The MainModule property lets you view information about the executable used to start the process, including the module name, file name, and module memory details.</remarks>
        public ProcessModule MainModule
        {
            get { return mProcess.MainModule; }
        }

        /// <summary>
        /// Gets the modules that have been loaded by the associated process. 
        /// </summary>
        /// <value>An array of type ProcessModule that represents the modules that have been loaded by the associated process.</value>
        /// <exception cref="NotSupportedException">You are attempting to access the Modules property for a process that is running on a remote computer. The property is available only for processes running on the local computer, or, the process Id is not available. </exception>
        public ProcessModuleCollection Modules 
        {
            get { return mProcess.Modules; }
        }

        /// <summary>
        /// Gets or sets the overall priority category for the associated process.
        /// </summary>
        /// <value>The priority category for the associated process, from which the BasePriority of the process is calculated.</value>
        /// <exception cref="Win32Exception">Process priority information could not be set or retrieved from the associated process resource -or- the process identifier or process handle is zero. (The process has not been started.)</exception>
        /// <exception cref="SystemException">You are attempting to access the PriorityClass property for a process that is running on a remote computer. The property is available only for processes running on the local computer -or- the process Id was not available.</exception>
        public ProcessPriorityClass PriorityClass 
        {
            get { return mProcess.PriorityClass; }
            set { mProcess.PriorityClass = value; }
        }

        /// <summary>
        /// Gets the privileged processor time for this process. 
        /// </summary>
        /// <value>A TimeSpan that indicates the amount of time that the process has spent running code inside the operating system core.</value>
        public TimeSpan PrivilegedProcessorTime 
        {
            get { return mProcess.PrivilegedProcessorTime; }
        }

        /// <summary>
        /// Gets the name of the process. 
        /// </summary>
        /// <value>The name that the system uses to identify the process to the user.</value>
        /// <exception cref="SystemException">The process does not have an identifier, or no process is associated with the Process -or- the associated process has exited. </exception>
        public string ProcessName 
        {
            get { return mProcess.ProcessName; }
        }

        /// <summary>
        /// Gets or sets the processors on which the threads in this process can be scheduled to run. 
        /// </summary>
        /// <value>A bitmask representing the processors that the threads in the associated process can run on. The default depends on the number of processors on the computer. The default value is 2 n -1, where n is the number of processors. </value>
        /// <exception cref="Win32Exception">ProcessorAffinity information could not be set or retrieved from the associated process resource -or- the process identifier or process handle is zero. (The process has not been started.) </exception>
        /// <exception cref="SystemException">The process Id was not available -or- the process has exited. </exception>
        public IntPtr ProcessorAffinity 
        {
            get { return mProcess.ProcessorAffinity; }
            set { mProcess.ProcessorAffinity = value; } 
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="ConsoleProcess"/> is responding.
        /// </summary>
        /// <value><c>true</c> if responding; otherwise, <c>false</c>.</value>
        /// <exception cref="InvalidOperationException">There is no process associated with this Process object.</exception>
        public bool Responding 
        {
            get
            {
                return mProcess.Responding;
            }   
        }

        /// <summary>
        /// Gets a stream used to read the error output of the application. 
        /// </summary>
        /// <value>An <see cref="AsyncStreamReader"/> that can be used to read the standard error output stream of the application.</value>
        /// <exception cref="InvalidOperationException">The StandardError stream has not been defined for redirection; ensure <see cref="ConsoleProcessStartInfo.RedirectStandardError"/> is set to <c>true</c></exception>
        public AsyncStreamReader StandardOutput
        {
            get
            {
                if (mStandardOutput == null)
                    throw new InvalidOperationException("StandardOutput has not beed redirected");

                return mStandardOutput;
            }
        }

        /// <summary>
        /// Gets a stream used to read the output of the application. 
        /// </summary>
        /// <value>An <see cref="AsyncStreamReader"/> that can be used to read the standard output stream of the application.</value>
        /// <exception cref="InvalidOperationException">The StandardOutput stream has not been defined for redirection; ensure <see cref="ConsoleProcessStartInfo.RedirectStandardOutput"/> is set to <c>true</c></exception>
        public AsyncStreamReader StandardError
        {
            get
            {
                if (mStandardError == null)
                    throw new InvalidOperationException("StandardError has not beed redirected");

                return mStandardError;
            }
        }

        /// <summary>
        /// Gets a stream used to write the input of the application. 
        /// </summary>
        /// <value>A StreamWriter that can be used to write the standard input stream of the application.</value>
        /// <exception cref="InvalidOperationException">The StandardInput stream has not been defined because <see cref="ConsoleProcessStartInfo.RedirectStandardInput"/> is set to false. </exception>
        public StreamWriter StandardInput
        {
            get
            {
                if (mStandardInput == null)
                    throw new InvalidOperationException("StandardInput has not beed redirected");

                return mStandardInput;
            }
        }


        /// <summary>
        /// Gets or sets the properties to pass to the <see cref="M:Start" /> method of the <see cref="ConsoleProcess"/>. 
        /// </summary>
        /// <value>The <see cref="ConsoleProcessStartInfo"/> that represents the data with which to start the process. These arguments include the name of the executable file used to start the process. </value>
        /// <exception cref="ArgumentNullException">The value that specifies the StartInfo is a <c>null</c> reference.</exception>
        public ConsoleProcessStartInfo StartInfo
        {
            get { return mStartInfo; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                mStartInfo = value;
            }
        }

        /// <summary>
        /// Gets the time that the associated process was started. 
        /// </summary>
        /// <value>A DateTime that indicates when the process started. This only has meaning for started processes.</value>
        public DateTime StartTime 
        {
            get { return mProcess.StartTime; }
        }

        /// <summary>
        /// Gets the total processor time for this process. 
        /// </summary>
        /// <value>A TimeSpan that indicates the amount of time that the associated process has spent utilizing the CPU. This value is the sum of the UserProcessorTime and the PrivilegedProcessorTime.</value>
        public TimeSpan TotalProcessorTime 
        {
            get { return mProcess.TotalProcessorTime; }
        }

        /// <summary>
        /// Gets the user processor time for this process. 
        /// </summary>
        /// <value>A TimeSpan that indicates the amount of time that the associated process has spent running code inside the application portion of the process (not inside the operating system core).</value>
        public TimeSpan UserProcessorTime 
        {
            get { return mProcess.UserProcessorTime; }
        }

       
        #endregion

        #region Public methods

        /// <summary>
        /// Closes this instance and releases any resources associated with it.
        /// </summary>
        public void Close()
        {
            Dispose(true);
        }

        /// <summary>
        /// Immediately stops the associated process. 
        /// </summary>
        /// <exception cref="Win32Exception">The associated process could not be terminated.
        /// -or-
        /// The process is terminating.
        /// -or- 
        /// The associated process is a Win16 executable.</exception>
        /// <exception cref="SystemException">No process Id has been set, and a Handle from which the Id property can be determined does not exist.
        /// -or- 
        /// There is no process associated with this Process object.
        /// -or- 
        /// You are attempting to call Kill for a process that is running on a remote computer. The method is available only for processes running on the local computer. 
        /// </exception>
        /// <exception cref="InvalidOperationException">The process has already exited.</exception>
        public void Kill()
        {
            mProcess.Kill();
        }

        /// <summary>
        /// Discards any information about the associated process that has been cached inside the process component. 
        /// </summary>
        /// <remarks><para>After Refresh is called, the first request for information about each property causes the process component to obtain a new value from the associated process.</para>
        /// <para>When a Process component is associated with a process resource, the property values of the Process are immediately populated according to the status of the associated process. If the information about the associated process subsequently changes, those changes are not reflected in the Process component's cached values. The Process component is a snapshot of the process resource at the time they are associated. To view the current values for the associated process, call the Refresh method.</para>
        /// </remarks>
        public void Refresh()
        {
            mProcess.Refresh();
        }

        /// <summary>
        /// Instructs the Process component to wait indefinitely for the associated process to exit. 
        /// </summary>
        /// <exception cref="Win32Exception">The wait setting could not be accessed. </exception>
        /// <exception cref="SystemException">No process Id has been set, and a Handle from which the Id property can be determined does not exist.
        /// -or- 
        /// There is no process associated with this Process object.
        /// -or- 
        /// You are attempting to call WaitForExit for a process running on a remote computer. The method is available only for processes that are running on the local computer. 
        /// </exception>
        public void WaitForExit()
        {
            mProcess.WaitForExit();
        }

        /// <summary>
        /// Instructs the Process component to wait the specified number of milliseconds for the associated process to exit. 
        /// </summary>
        /// <param name="milliseconds">The amount of time, in milliseconds, to wait for the associated process to exit. The maximum is the largest possible value of a 32-bit integer, which represents infinity to the operating system.</param>
        /// <returns>true if the associated process has exited; otherwise, false. </returns>
       /// <exception cref="Win32Exception">The wait setting could not be accessed. </exception>
        /// <exception cref="SystemException">No process Id has been set, and a Handle from which the Id property can be determined does not exist.
        /// -or- 
        /// There is no process associated with this Process object.
        /// -or- 
        /// You are attempting to call WaitForExit for a process running on a remote computer. The method is available only for processes that are running on the local computer. 
        /// </exception>
        public bool WaitForExit(int milliseconds)
        {
            return mProcess.WaitForExit(milliseconds);
        }

        /// <summary>
        /// Starts the process resource that is specified by the parameter containing process 
        /// start information (for example, the file name of the process to start) and associates the 
        /// resource with a new ConsoleProcess component. 
        /// </summary>
        /// <param name="startInfo">The <see cref="ConsoleProcessStartInfo"/> that contains the information that is used to start the process, including the file name and any command-line arguments. </param>
        /// <returns>A new <see cref="ConsoleProcess"/> component that is associated with the process resource, or a <c>null</c> 
        /// reference (Nothing in Visual Basic) if no process resource is started (for example, if an existing process 
        /// is reused).</returns>
        /// <exception cref="InvalidOperationException">No file name was specified in the startInfo parameter's FileName property.
        /// -or- 
        /// Any conflicting parameters are set in the <see cref="ConsoleProcessStartInfo"/> object
        /// </exception>
        /// <exception cref="ArgumentException">The startInfo parameter is a null reference (Nothing in Visual Basic).</exception>
        /// <exception cref="Win32Exception">There was an error in opening the associated file. </exception>
        /// <exception cref="ObjectDisposedException">The process object has already been disposed.</exception>
        public static ConsoleProcess Start(ConsoleProcessStartInfo startInfo)
        {
            ConsoleProcess process = new ConsoleProcess();
            process.StartInfo = startInfo;
            process.Start();
            return process;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Starts (or reuses) the process resource that is specified by the <see cref="StartInfo"/> property of this
        /// <see cref="ConsoleProcess"/> component and associates it with the component.
        /// </summary>
        /// <returns><c>true</c> if a process resource is started; <c>false</c> if no new process resource is started (for example, if an existing process is reused). </returns>
        /// <exception cref="InvalidOperationException">No file name was specified in the startInfo parameter's FileName property.
        /// -or- 
        /// Any conflicting parameters are set in the <see cref="ConsoleProcessStartInfo"/> object
        /// </exception>
        /// <exception cref="Win32Exception">There was an error in opening the associated file. </exception>
        /// <exception cref="ObjectDisposedException">The process object has already been disposed.</exception>
        public bool Start()
        {
            UnsafeNativeMethods.STARTUPINFO startupInfo = new UnsafeNativeMethods.STARTUPINFO();
            startupInfo.cb = Marshal.SizeOf(startupInfo);
            startupInfo.lpReserved = null;
            startupInfo.lpDesktop = null;
            startupInfo.lpTitle = StartInfo.ConsoleWindowTitle;
            startupInfo.cbReserved2 = 0;

            if (StartInfo.RedirectStandardError || StartInfo.RedirectStandardOutput || StartInfo.RedirectStandardInput)
            {
                startupInfo.dwFlags = (int)UnsafeNativeMethods.StartF.STARTF_USESTDHANDLES;
                if (!StartInfo.InheritHandles)
                    throw new InvalidOperationException("StartInfo.InheritHandles can not be set to false if any standard stream is redirected");
            }
            else
            {
                startupInfo.dwFlags = 0;
            }


            AnonymousPipeServerStream errorPipeServerStream = null;
            AnonymousPipeServerStream outputPipeServerStream = null;
            AnonymousPipeServerStream inputPipeServerStream = null;

            if (StartInfo.RedirectStandardError)
            {
                errorPipeServerStream = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);
                mStandardError = new AsyncStreamReader(errorPipeServerStream, StartInfo.StandardErrorEncoding != null ? StartInfo.StandardErrorEncoding : Encoding.Default);
                startupInfo.hStdError = errorPipeServerStream.ClientHandle.DangerousGetHandle();
                mStandardError.DataReceived += ErrorDataReceived;
            }
            else
            {
                startupInfo.hStdError = UnsafeNativeMethods.GetStdHandle(UnsafeNativeMethods.STD_ERROR_HANDLE);
            }

            if (StartInfo.RedirectStandardOutput)
            {
                outputPipeServerStream = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);
                mStandardOutput = new AsyncStreamReader(outputPipeServerStream, StartInfo.StandardOutputEncoding != null ? StartInfo.StandardOutputEncoding : Encoding.Default);
                startupInfo.hStdOutput = outputPipeServerStream.ClientHandle.DangerousGetHandle();
                mStandardOutput.DataReceived += OutputDataReceived;
            }
            else
            {
                startupInfo.hStdOutput = UnsafeNativeMethods.GetStdHandle(UnsafeNativeMethods.STD_OUTPUT_HANDLE);
            }

            if (StartInfo.RedirectStandardInput)
            {
                inputPipeServerStream = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable);
                mStandardInput = new StreamWriter(inputPipeServerStream, StartInfo.StandardInputEncoding != null ? StartInfo.StandardInputEncoding : Encoding.Default);
                mStandardInput.AutoFlush = true;
                startupInfo.hStdInput = inputPipeServerStream.ClientHandle.DangerousGetHandle();
            }
            else
            {
                startupInfo.hStdInput = UnsafeNativeMethods.GetStdHandle(UnsafeNativeMethods.STD_INPUT_HANDLE);
            }

            UnsafeNativeMethods.PROCESS_INFORMATION procInfo = new UnsafeNativeMethods.PROCESS_INFORMATION();

            UnsafeNativeMethods.SECURITY_ATTRIBUTES processAttrinbutes = new UnsafeNativeMethods.SECURITY_ATTRIBUTES();
            processAttrinbutes.nLength = Marshal.SizeOf(processAttrinbutes);

            UnsafeNativeMethods.SECURITY_ATTRIBUTES threadAttributes = new UnsafeNativeMethods.SECURITY_ATTRIBUTES();
            threadAttributes.nLength = Marshal.SizeOf(threadAttributes);

            processAttrinbutes.bInheritHandle = StartInfo.InheritHandles;
            threadAttributes.bInheritHandle = StartInfo.InheritHandles;

            string applicationName = StartInfo.FileName;
            string commandLine;
            if (!(applicationName.StartsWith("\"") && applicationName.EndsWith("\"")) && applicationName.Contains(" "))
                commandLine = "\"" + applicationName + "\"";
            else
                commandLine = applicationName;

            commandLine += " " + StartInfo.Arguments;

            string environmentVariables = null;
            if (StartInfo.EnvironmentVariables != null)
            {
                environmentVariables = "";
                foreach (System.Collections.DictionaryEntry kvp in StartInfo.EnvironmentVariables)
                {
                    environmentVariables += kvp.Key.ToString() + "=" + kvp.Value.ToString() + "\0";
                }
            }

            if (!UnsafeNativeMethods.CreateProcess(applicationName,
                                            commandLine,
                                            ref processAttrinbutes,
                                            ref threadAttributes,
                                            StartInfo.InheritHandles,
                                            StartInfo.CreationFlags,
                                            environmentVariables,
                                            StartInfo.WorkingDirectory,
                                            ref startupInfo,
                                            out procInfo))
            {
                throw new System.ComponentModel.Win32Exception();
            }

            if (StartInfo.RedirectStandardError)
                errorPipeServerStream.DisposeLocalCopyOfClientHandle();

            if (StartInfo.RedirectStandardInput)
                inputPipeServerStream.DisposeLocalCopyOfClientHandle();

            if (StartInfo.RedirectStandardOutput)
                outputPipeServerStream.DisposeLocalCopyOfClientHandle();

            try
            {
                mHandle = new SafeFileHandle(procInfo.hProcess, true);
                mProcess = Process.GetProcessById(procInfo.dwProcessId);
            }
            catch (ArgumentException)
            {
                return false;
            }

            return true;

        }

        /// <summary>
        /// Sends a specified signal to a console process group that shares the console associated with the calling process.
        /// </summary>
        /// <param name="controlEvent">The control event.</param>
        /// <param name="processGroupId">The process group id.</param>
        public static void GenerateConsoleControlEvent(ControlEvent controlEvent, int processGroupId)
        {
            if (!UnsafeNativeMethods.GenerateConsoleCtrlEvent((int)controlEvent, processGroupId))
            {
                throw new Win32Exception();
            }
        }

        /// <summary>
        /// Generates a console break event, equivalent to pressing Ctrl-Break in the console for the 
        /// current process.
        /// </summary>
        /// <remarks>This is only valid if the process was created with <see cref="ConsoleProcessStartInfo.CreateNewConsole"/>
        /// set to <c>false</c>, <see cref="ConsoleProcessStartInfo.CreateNoWindow"/> set to <c>false</c> and 
        /// <see cref="P:ConsoleProcessStartInfo.CreateNewProcessGroup"/> set to <c>true</c> when the 
        /// process was created.</remarks>
        public void GenerateBreak()
        {
            GenerateConsoleControlEvent(ControlEvent.Break, Id);
        }

        /// <summary>
        /// Begins asynchronous read operations on the redirected <see cref="StandardError"/> stream of the application.
        /// </summary>
        /// <exception cref="InvalidOperationException">The process has not been started, or, <see cref="ConsoleProcessStartInfo.RedirectStandardError"/> is not set to <c>true</c></exception>
        public void BeginErrorReadLine()
        {
            if (mStandardError == null)
                throw new InvalidOperationException("Process has not been started");

            mStandardError.BeginReadLine();
        }

        /// <summary>
        /// Begins asynchronous read operations on the redirected <see cref="StandardOutput"/> stream of the application.
        /// </summary>
        /// <exception cref="InvalidOperationException">The process has not been started, or, <see cref="ConsoleProcessStartInfo.RedirectStandardOutput"/> is not set to <c>true</c></exception>
        public void BeginOutputReadLine()
        {
            if (mStandardOutput == null)
                throw new InvalidOperationException("Process has not been started");

            mStandardOutput.BeginReadLine();
        }

        /// <summary>
        /// Cancels the asynchronous read operation on the redirected <see cref="StandardError"/> stream of an application. 
        /// </summary>
        /// <exception cref="InvalidOperationException">The process has not been started, or, <see cref="ConsoleProcessStartInfo.RedirectStandardError"/> is not set to <c>true</c></exception>
        public void CancelErrorRead()
        {
            if (mStandardError == null)
                throw new InvalidOperationException("Process has not been started");

            mStandardError.CancelReadLine();
        }

        /// <summary>
        /// Cancels the asynchronous read operation on the redirected <see cref="StandardOutput"/> stream of an application. 
        /// </summary>
        /// <exception cref="InvalidOperationException">The process has not been started, or, <see cref="ConsoleProcessStartInfo.RedirectStandardOutput"/> is not set to <c>true</c></exception>
        public void CancelOutputRead()
        {
            if (mStandardOutput == null)
                throw new InvalidOperationException("Process has not been started");

            mStandardOutput.CancelReadLine();
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Occurs when a process exits. 
        /// </summary>
        /// <remarks><para>The Exited event indicates that the associated process exited. This occurrence means either that the process terminated (aborted) or successfully closed.</para>
        /// <para>There are two ways of being notified when the associated process exits: synchronously and asynchronously. Synchronous notification relies on calling the WaitForExit method to pause the processing of your application until the associated component exits. Asynchronous notification relies on the Exited event. In either case, EnableRaisingEvents must be set to true for the Process component to receive notification that the process has exited.</para>
        /// <para>When the operating system shuts down a process, any process component that is waiting for an exit is notified. The component can then access the associated process information that is still resident in the operating system memory (such as ExitTime property) by using the handle that it has to the process.</para>
        /// <para>Because the associated process has exited, the Handle property of the component no longer points to an existing process resource. Instead, it can be used only to access the operating system's information about the process resource. The system is aware of handles to exited processes that have not been released by Process components, so it keeps the ExitTime and Handle property information in memory until the Process component specifically frees the resources.</para>
        /// </remarks>
        public event EventHandler Exited;

        /// <summary>
        /// Occurs when output data is received and BeginOutputReadLine() has been called.
        /// </summary>
        public event EventHandler<AsyncDataReceivedEventArgs> OutputDataReceived;


        /// <summary>
        /// Occurs when error data is received and BeginErrorReadLine() has been called.
        /// </summary>
        public event EventHandler<AsyncDataReceivedEventArgs> ErrorDataReceived;

        #endregion

        #region Protected methods

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (mStandardError != null)
                mStandardError.Dispose();

            if (mStandardInput != null)
                mStandardInput.Dispose();

            if (mStandardOutput != null)
                mStandardOutput.Dispose();

            if (mHandle != null)
                mHandle.Dispose();

            mProcess.Dispose();

        }

        #endregion 

        #region Private fields

        private Process mProcess;
        private ConsoleProcessStartInfo mStartInfo;
        private AsyncStreamReader mStandardOutput;
        private AsyncStreamReader mStandardError;
        private StreamWriter mStandardInput;
        SafeHandle mHandle;
        #endregion


    }
}
